/*
 * SWF.java
 * Describe SWF
 *
 * A browser for Flash files.
 * Copyright (c) 2001-2006 Flagstone Software Ltd. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * For more information please contact:
 *
 * Stuart MacKay
 * Flagstone Software Ltd
 * 92 High Street
 * Wick, Caithness KW1 4LY
 * Scotland
 *
 * www.flagstonesoftware.com
 */
package com.flagstone.describe;

final class SWF
{
    /*
     * Fill Styles
     */
    static final int Solid = 0; // Solid
    static final int Linear  = 16; // Gradient
    static final int Radial  = 18; // Gradient
    static final int Tiled = 64; // Bitmap
    static final int Clipped = 65; // Bitmap
    /*
     * Fill Styles added in Flash 7
     */
    static final int Unsmoothed_Tiled = 66; // Bitmap
    static final int Unsmoothed_Clipped = 67; // Bitmap
    /*
     * Instructions added in Flash 1
     */
    static final int ShowFrame            = 1;
    static final int DefineShape          = 2;
    static final int PlaceObject          = 4;
    static final int RemoveObject         = 5;
    static final int DefineJPEGImage      = 6;
    static final int DefineButton         = 7;
    static final int JPEGTables           = 8;
    static final int SetBackgroundColor   = 9;
    static final int DefineFont           = 10;
    static final int DefineText           = 11;
    static final int DoAction             = 12;
    static final int FontInfo             = 13;
    static final int DefineSound          = 14;
    static final int StartSound           = 15;
    static final int SoundStreamHead      = 18;
    static final int SoundStreamBlock     = 19;
    /*
     * Instructions added in Flash 2
     */
    private static final int ButtonSound          = 17;
    private static final int DefineImage          = 20;
    private static final int DefineJPEGImage2     = 21;
    private static final int DefineShape2         = 22;
    private static final int ButtonColorTransform = 23;
    private static final int Protect              = 24;
    /*
     * Instructions added in Flash 3
     */
    private static final int Free                 = 3;
    private static final int PlaceObject2         = 26;
    private static final int RemoveObject2        = 28;
    private static final int DefineShape3         = 32;
    private static final int DefineText2          = 33;
    private static final int DefineButton2        = 34;
    private static final int DefineJPEGImage3     = 35;
    private static final int DefineImage2         = 36;
    private static final int DefineMovieClip      = 39;
    private static final int FrameLabel           = 43;
    private static final int SoundStreamHead2     = 45;
    private static final int DefineMorphShape     = 46;
    private static final int DefineFont2          = 48;
    /*
     * Instructions added in Flash 4
     */
    private static final int PathsArePostscript   = 25;
    private static final int DefineTextField      = 37;
    private static final int QuicktimeMovie       = 38;
    private static final int SerialNumber         = 41;
    private static final int DefineBitsPtr        = 1023;
    /*
     * Instructions added in Flash 5
     */
    private static final int Export               = 56;
    private static final int Import               = 57;
    private static final int EnableDebugger       = 58;
    /*
     * Instructions added in Flash 6
     */
    private static final int Initialize           = 59;
    private static final int DefineVideo          = 60;
    private static final int VideoFrame           = 61;
    private static final int FontInfo2            = 62;
    private static final int EnableDebugger2      = 64;
    /*
     * Instructions added in Flash 7
     */
    private static final int LimitScript          = 65;
    private static final int TabOrder             = 66;


    /*
     * Stack-based actions added in Flash 1
     */
    private static final int End              = 0;
    private static final int NextFrame        = 4;
    private static final int PrevFrame        = 5;
    private static final int Play             = 6;
    private static final int Stop             = 7;
    private static final int ToggleQuality    = 8;
    private static final int StopSounds       = 9;
    /*
     * Stack-based actions added in Flash 4
     */
    private static final int IntegerAdd       = 10;
    private static final int Subtract         = 11;
    private static final int Multiply         = 12;
    private static final int Divide           = 13;
    private static final int IntegerEquals    = 14;
    private static final int IntegerLess      = 15;
    private static final int And              = 16;
    private static final int Or               = 17;
    private static final int Not              = 18;
    private static final int StringEquals     = 19;
    private static final int StringLength     = 20;
    private static final int StringExtract    = 21;
    private static final int Pop              = 23;
    private static final int ToInteger        = 24;
    private static final int GetVariable      = 28;
    private static final int SetVariable      = 29;
    private static final int SetTarget2       = 32;
    private static final int StringAdd        = 33;
    private static final int GetProperty      = 34;
    private static final int SetProperty      = 35;
    private static final int CloneSprite      = 36;
    private static final int RemoveSprite     = 37;
    private static final int Trace            = 38;
    private static final int StartDrag        = 39;
    private static final int EndDrag          = 40;
    private static final int StringLess       = 41;
    private static final int RandomNumber     = 48;
    private static final int MBStringLength   = 49;
    private static final int CharToAscii      = 50;
    private static final int AsciiToChar      = 51;
    private static final int GetTime          = 52;
    private static final int MBStringExtract  = 53;
    private static final int MBCharToAscii    = 54;
    private static final int MBAsciiToChar    = 55;
    /*
     * Stack-based actions added in Flash 5
     */
    private static final int DeleteVariable   = 58;
    private static final int Delete           = 59;
    private static final int InitVariable     = 60;
    private static final int ExecuteFunction  = 61;
    private static final int Return           = 62;
    private static final int Modulo           = 63;
    private static final int NamedObject      = 64;
    private static final int NewVariable      = 65;
    private static final int NewArray         = 66;
    private static final int NewObject        = 67;
    private static final int GetType          = 68;
    private static final int GetTarget        = 69;
    private static final int Enumerate        = 70;
    private static final int Add              = 71;
    private static final int Less             = 72;
    private static final int Equals           = 73;
    private static final int ToNumber         = 74;
    private static final int ToString         = 75;
    private static final int Duplicate        = 76;
    private static final int Swap             = 77;
    private static final int GetAttribute     = 78;
    private static final int SetAttribute     = 79;
    private static final int Increment        = 80;
    private static final int Decrement        = 81;
    private static final int ExecuteMethod    = 82;
    private static final int NewMethod        = 83;
    private static final int BitwiseAnd       = 96;
    private static final int BitwiseOr        = 97;
    private static final int BitwiseXOr       = 98;
    private static final int LogicalShiftLeft = 99;
    private static final int ArithmeticShiftRight = 100;
    private static final int LogicalShiftRight    = 101;
    /*
     * Stack-based actions added in Flash 6
     */
    private static final int InstanceOf      = 84;
    private static final int EnumerateObject = 85;
    private static final int StrictEquals    = 102;
    private static final int Greater         = 103;
    private static final int StringGreater   = 104;
    /*
     * Stack-based actions added in Flash 7
     */
    private static final int Throw      = 42;
    private static final int Cast       = 43;
    private static final int Implements = 44;
    private static final int Extends    = 105;


    /*
     * Non stack-based actions added in Flash 1
     */
    private static final int GotoFrame     = 129;
    private static final int GetUrl        = 131;
    /*
     * Non stack-based actions added in Flash 3
     */
    private static final int WaitForFrame  = 138;
    private static final int SetTarget     = 139;
    private static final int GotoLabel     = 140;
    /*
     * Non stack-based actions added in Flash 4
     */
    private static final int WaitForFrame2 = 141;
    private static final int Push          = 150;
    private static final int Jump          = 153;
    private static final int GetUrl2       = 154;
    private static final int If            = 157;
    private static final int Call          = 158;
    private static final int GotoFrame2    = 159;
    /*
     * Non stack-based actions added in Flash 5
     */
    private static final int RegisterCopy  = 135;
    private static final int Table         = 136;
    private static final int With          = 148;
    private static final int NewFunction   = 155;
    /*
     * Non stack-based actions added in Flash 7
     */
    private static final int NewFunction2     = 142;
    private static final int ExceptionHandler = 143;

    static void decodeMovie(byte[] bytes, MovieListener listener)
    {
        SWFDecoder coder = null;

        coder = new SWFDecoder(bytes, listener);

        coder.beginObject("header", "Header");

        coder.readString("signature", 3); // signature
        int version = coder.readWord("version", 1, false); // version
        coder.readWord("length", 4, false); // length
        SWF.decodeBounds("frameSize", coder); // frameSize
        coder.readFixedWord("frameRate", 16, 256, false); // frameRate
        coder.readWord("frameCount", 2, false); // frameCount

        coder.endObject("Header");

        coder.context[SWFDecoder.Version] = version;

        while (SWF.decodeObject(coder));
    }

    static boolean decodeObject(SWFDecoder coder)
    {
        int type = coder.scanWord(2, false) >> 6;

        if (type == 0)
            return false;

        int length = coder.scanWord(2, false) & 0x3F;
        int next = coder.getPointer() + 16 + (length << 3);

        if (length == 0x3F)
        {
            coder.adjustPointer(16);
            length = coder.scanWord(4, false);
            next = coder.getPointer() + 32 + (length << 3);
            coder.adjustPointer(-16);
        }

        boolean decoded = false;

        // Flash 1
        switch (type)
        {
            case ShowFrame:
                decodeShowFrame(coder);
                decoded = true;
                break;
            case DefineShape:
                decodeDefineShape(coder);
                decoded = true;
                break;
            case PlaceObject:
                decodePlaceObject(coder);
                decoded = true;
                break;
            case RemoveObject:
                decodeRemoveObject(coder);
                decoded = true;
                break;
            case DefineJPEGImage:
                decodeDefineJPEGImage(coder);
                decoded = true;
                break;
            case DefineButton:
                decodeDefineButton(coder);
                decoded = true;
                break;
            case JPEGTables:
                decodeJPEGEncodingTable(coder);
                decoded = true;
                break;
            case SetBackgroundColor:
                decodeSetBackgroundColor(coder);
                decoded = true;
                break;
            case DefineFont:
                decodeDefineFont(coder);
                decoded = true;
                break;
            case DefineText:
                decodeDefineText(coder);
                decoded = true;
                break;
            case DoAction:
                decodeDoAction(coder);
                decoded = true;
                break;
            case FontInfo:
                decodeFontInfo(coder);
                decoded = true;
                break;
            case DefineSound:
                decodeDefineSound(coder);
                decoded = true;
                break;
            case StartSound:
                decodeStartSound(coder);
                decoded = true;
                break;
            case SoundStreamHead:
                decodeSoundStreamHead(coder);
                decoded = true;
                break;
            case SoundStreamBlock:
                decodeSoundStreamBlock(coder);
                decoded = true;
                break;
            // Flash 2
            case ButtonSound:
                decodeButtonSound(coder);
                decoded = true;
                break;
            case DefineImage:
                decodeDefineImage(coder);
                decoded = true;
                break;
            case DefineJPEGImage2:
                decodeDefineJPEGImage2(coder);
                decoded = true;
                break;
            case DefineShape2:
                decodeDefineShape2(coder);
                decoded = true;
                break;
            case ButtonColorTransform:
                decodeButtonColorTransform(coder);
                decoded = true;
                break;
            case Protect:
                decodeProtect(coder);
                decoded = true;
                break;
                // Flash 3
            case Free:
                decodeFree(coder);
                decoded = true;
                break;
            case PlaceObject2:
                decodePlaceObject2(coder);
                decoded = true;
                break;
            case RemoveObject2:
                decodeRemoveObject2(coder);
                decoded = true;
                break;
            case DefineShape3:
                decodeDefineShape3(coder);
                decoded = true;
                break;
            case DefineText2:
                decodeDefineText2(coder);
                decoded = true;
                break;
            case DefineButton2:
                decodeDefineButton2(coder);
                decoded = true;
                break;
            case DefineJPEGImage3:
                decodeDefineJPEGImage3(coder);
                decoded = true;
                break;
            case DefineImage2:
                decodeDefineImage2(coder);
                decoded = true;
                break;
            case DefineMovieClip:
                decodeDefineMovieClip(coder);
                decoded = true;
                break;
            case FrameLabel:
                decodeFrameLabel(coder);
                decoded = true;
                break;
            case SoundStreamHead2:
                decodeSoundStreamHead2(coder);
                decoded = true;
                break;
            case DefineMorphShape:
                decodeDefineMorphShape(coder);
                decoded = true;
                break;
            case DefineFont2:
                decodeDefineFont2(coder);
                decoded = true;
                break;
                // Flash 4
            case PathsArePostscript:
                decodePathsArePostscript(coder);
                decoded = true;
                break;
            case DefineTextField:
                decodeDefineTextField(coder);
                decoded = true;
                break;
            case QuicktimeMovie:
                decodeQuicktimeMovie(coder);
                decoded = true;
                break;
            case SerialNumber:
                decodeSerialNumber(coder);
                decoded = true;
                break;
            case DefineBitsPtr:
                decodePointer(coder);
                decoded = true;
                break;
                // Flash 5
            case EnableDebugger:
                decodeEnableDebugger(coder);
                decoded = true;
                break;
            case Export:
                decodeExport(coder);
                decoded = true;
                break;
            case Import:
                decodeImport(coder);
                decoded = true;
                break;
                // Flash 6
            case DefineVideo:
                decodeDefineVideo(coder);
                decoded = true;
                break;
            case VideoFrame:
                decodeVideoFrame(coder);
                decoded = true;
                break;
            case EnableDebugger2:
                decodeEnableDebugger2(coder);
                decoded = true;
                break;
            case Initialize:
                decodeInitialize(coder);
                decoded = true;
                break;
            case FontInfo2:
                decodeFontInfo2(coder);
                decoded = true;
                break;
                // Flash 7
            case LimitScript:
                decodeLimitScript(coder);
                decoded = true;
                break;
            case TabOrder:
                decodeTabOrder(coder);
                decoded = true;
                break;
            default:
                break;
        }

        if (decoded == false)
            decodeUnknownObject(coder);

        int delta = (next - coder.getPointer()) >> 3;

        if (delta < 0)
            coder.logError("ObjectOverflow", next, -delta);
        else if (delta > 0)
            coder.logError("ObjectUnderflow", next, delta);

        coder.setPointer(next);

        return true;
    }

    static void decodeBounds(String name, SWFDecoder coder)
    {
        coder.beginObject(name, "Bounds");
        coder.alignToByte();

        int fieldSize = coder.readBits("size", 5, false);

        coder.readBits("minX", fieldSize, true);
        coder.readBits("maxX", fieldSize, true);
        coder.readBits("minY", fieldSize, true);
        coder.readBits("maxY", fieldSize, true);

        coder.alignToByte();

        coder.endObject("Bounds");
    }

    static void decodeColor(String name, SWFDecoder coder)
    {
        coder.beginObject(name, "Color");

        coder.readWord("red", 1, false);
        coder.readWord("green", 1, false);
        coder.readWord("blue", 1, false);

        if (coder.context[SWFDecoder.TransparentColors] != 0)
            coder.readWord("alpha", 1, false);

        coder.endObject("Color");
    }

    static void decodeColorTransform(String name, SWFDecoder coder)
    {
        coder.beginObject(name, "ColorTransform");
        coder.alignToByte();

        boolean containsAddTerms = coder.readBool("hasAddTerms");
        boolean containsMultiplyTerms = coder.readBool("hasMultiplyTerms");
        int fieldSize = coder.readBits("size", 4, false);

        if (containsMultiplyTerms)
        {
            coder.readShortFixed("multiplyRed", fieldSize);
            coder.readShortFixed("multiplyGreen", fieldSize);
            coder.readShortFixed("multiplyBlue", fieldSize);

            if (coder.context[SWFDecoder.TransparentColors] != 0)
                coder.readShortFixed("multiplyAlpha", fieldSize);
        }

        if (containsAddTerms)
        {
            coder.readBits("addRed", fieldSize, true);
            coder.readBits("addGreen", fieldSize, true);
            coder.readBits("addBlue", fieldSize, true);

            if (coder.context[SWFDecoder.TransparentColors] != 0)
                coder.readBits("addAlpha", fieldSize, true);
        }
        coder.alignToByte();
        coder.endObject("ColorTransform");
    }

    static void decodeCoordTransform(String name, SWFDecoder coder)
    {
        coder.beginObject(name, "CoordTransform");
        coder.alignToByte();

        if (coder.readBool("hasScaling"))
        {
            int scaleFieldSize = coder.readBits("scalingSize", 5, false);
            coder.readFixed("scale0", scaleFieldSize);
            coder.readFixed("scale1", scaleFieldSize);
        }

        if (coder.readBool("hasRotation"))
        {
            int rotateFieldSize = coder.readBits("rotationSize", 5, false);
            coder.readFixed("rotate0", rotateFieldSize);
            coder.readFixed("rotate1", rotateFieldSize);
        }

        int translateFieldSize = coder.readBits("translationSize", 5, false);
        coder.readBits("translateX", translateFieldSize, true);
        coder.readBits("translateY", translateFieldSize, true);

        coder.alignToByte();
        coder.endObject("CoordTransform");
    }

    static void decodeSound(SWFDecoder coder)
    {
        coder.beginObject("sound", "Sound");

        coder.readWord("identifier", 2, false);
        coder.readBits("mode", 4, false);

        boolean _containsEnvelopes = coder.readBool("hasEnvelopes");
        boolean _containsLoopCount = coder.readBool("hasLoopCount");
        boolean _containsOutPoint = coder.readBool("hasOutPoint");
        boolean _containsInPoint = coder.readBool("hasInPoint");

        if (_containsInPoint)
            coder.readWord("inPoint", 4, false);

        if (_containsOutPoint)
            coder.readWord("outPoint", 4, false);

        if (_containsLoopCount)
            coder.readWord("loopCount", 2, false);

        if (_containsEnvelopes)
        {
            int envelopeCount = coder.readWord("envelopeCount", 1, false);

            coder.beginObject("envelopes", "Array");

            for (int i=0; i<envelopeCount; i++)
                decodeEnvelope(coder);

            coder.endObject("Array");
        }
        coder.endObject("Sound");
    }

    static void decodeEnvelope(SWFDecoder coder)
    {
        coder.beginObject("envelope", "Envelope");
        coder.readWord("mark", 4, false);
        coder.readWord("leftSoundLevel", 2, false);
        coder.readWord("rightSoundLevel", 2, false);
        coder.endObject("Envelope");
    }

    static void decodeCurve(SWFDecoder coder)
    {
        coder.beginObject("curve", "Curve");

        coder.readBits("type", 2, false);

        int fieldSize = coder.readBits("size", 4, false) + 2;

        coder.readBits("cx", fieldSize, true);
        coder.readBits("cy", fieldSize, true);
        coder.readBits("ax", fieldSize, true);
        coder.readBits("ay", fieldSize, true);

        coder.endObject("Curve");
    }

    static void decodeLine(SWFDecoder coder)
    {
        coder.beginObject("line", "Line");

        coder.readBits("type", 2, false);

        int fieldSize = coder.readBits("size", 4, false)+2;

        if (coder.readBool("hasXY"))
        {
            coder.readBits("x", fieldSize, true);
            coder.readBits("y", fieldSize, true);
        }
        else
        {
            if (coder.readBool("hasY"))
            {
                coder.readBits("y", fieldSize, true);
            }
            else // horizontal line
            {
                coder.readBits("x", fieldSize, true);
            }
        }
        coder.endObject("Line");
    }

    static void decodeShapeStyle(SWFDecoder coder)
    {
        int numberOfFillBits = coder.context[SWFDecoder.NumberOfFillBits];
        int numberOfLineBits = coder.context[SWFDecoder.NumberOfLineBits];

        coder.beginObject("shapeStyle", "ShapeStyle");

        coder.readBits("type", 1, false);
        boolean containsStyles = coder.readBool("hasStyles");
        boolean containsLineStyle = coder.readBool("hasLineStyle");
        boolean containsAltFillStyle = coder.readBool("hasAltFillStyle");
        boolean containsFillStyle = coder.readBool("hasFillStyle");
        boolean containsMove = coder.readBool("hasMove");

        if (containsMove)
        {
            int moveFieldSize = coder.readBits("size", 5, false);
            coder.readBits("x", moveFieldSize, true);
            coder.readBits("y", moveFieldSize, true);
        }

        if (containsFillStyle)
            coder.readBits("fillStyle", numberOfFillBits, false);

        if (containsAltFillStyle)
            coder.readBits("altFillStyle", numberOfFillBits, false);

        if (containsLineStyle)
            coder.readBits("lineStyle", numberOfLineBits, false);

        if (containsStyles)
        {
            coder.alignToByte();

            int fillStyleCount = coder.readWord("fillStyleCount", 1, false);

            if (coder.context[SWFDecoder.ArrayCountExtended] != 0 && fillStyleCount == 0xFF)
                fillStyleCount = coder.readWord("fillStyleCountExtended", 2, false);

            coder.beginObject("fillStyles", "Array");

            for (int i=0; i<fillStyleCount; i++)
            {
                switch (coder.scanWord(1, false))
                {
                    case Solid:
                        decodeSolidFill(coder);
                        break;
                    case Linear:
                    case Radial:
                        decodeGradientFill(coder);
                        break;
                    case Tiled:
                    case Clipped:
                    case Unsmoothed_Tiled:
                    case Unsmoothed_Clipped:
                        decodeBitmapFill(coder);
                        break;
                }
            }

            coder.endObject("Array");

            int lineStyleCount = coder.readWord("lineStyleCount", 1, false);

            if (coder.context[SWFDecoder.ArrayCountExtended] != 0 && lineStyleCount == 0xFF)
                lineStyleCount = coder.readWord("lineStyleCountExtended", 2, false);

            coder.beginObject("lineStyles", "Array");

            for (int i=0; i<lineStyleCount; i++)
                decodeSolidLine(coder);

            coder.endObject("Array");

            numberOfFillBits = coder.readBits("numberOfFillBits", 4, false);
            numberOfLineBits = coder.readBits("numberOfLineBits", 4, false);

            coder.context[SWFDecoder.NumberOfFillBits] = numberOfFillBits;
            coder.context[SWFDecoder.NumberOfLineBits] = numberOfLineBits;
        }
        coder.endObject("ShapeStyle");
    }

    static void decodeSolidLine(SWFDecoder coder)
    {
        coder.beginObject("solidLine", "SolidLine");
        coder.readWord("width", 2, false);
        decodeColor("color", coder);
        coder.endObject("SolidLine");
    }

    static void decodeSolidFill(SWFDecoder coder)
    {
        coder.beginObject("solidFill", "SolidFill");
        coder.readWord("type", 1, false);
        decodeColor("color", coder);
        coder.endObject("SolidFill");
    }

    static void decodeGradientFill(SWFDecoder coder)
    {
        coder.beginObject("gradientFill", "GradientFill");
        coder.readWord("type", 1, false);
        decodeCoordTransform("coordTransform", coder);
        int count = coder.readWord("gradientCount", 1, false) & 0x0F;

        coder.beginObject("gradients", "Array");

        for (int i=0; i<count; i++)
            decodeGradient(coder);

        coder.endObject("Array");
        coder.endObject("GradientFill");
    }

    static void decodeGradient(SWFDecoder coder)
    {
        coder.beginObject("gradient", "Gradient");
        coder.readWord("ratio", 1, false);
        decodeColor("color", coder);
        coder.endObject("Gradient");
    }

    static void decodeBitmapFill(SWFDecoder coder)
    {
        coder.beginObject("bitmapFill", "BitmapFill");
        coder.readWord("type", 1, false);
        coder.readWord("identifier", 2, false);
        decodeCoordTransform("coordTransform", coder);
        coder.endObject("BitmapFill");
    }

    static void decodeShape(SWFDecoder coder)
    {
        coder.beginObject("shape", "Shape");

        coder.context[SWFDecoder.NumberOfFillBits] = coder.readBits("numberOfFillBits", 4, false);
        coder.context[SWFDecoder.NumberOfLineBits] = coder.readBits("numberOfLineBits", 4, false);

        coder.beginObject("path", "Array");

        while (coder.scanBits(6, false) > 0)
        {
            if ((coder.scanBits(6, false) & 0x20) > 0)
            {
                if ((coder.scanBits(6, false) & 0x10) > 0)
                    decodeLine(coder);
                else
                    decodeCurve(coder);
            }
            else
            {
                decodeShapeStyle(coder);
            }
        }
        coder.readBits("endOfShape", 6, false);
        coder.alignToByte();
        coder.endObject("Array");
        coder.endObject("Shape");
    }

    static int decodeMovieObject(SWFDecoder coder)
    {
        int type = coder.scanWord(2, false) >> 6;
        int length = coder.scanWord(2, false) & 0x3F;

        coder.context[SWFDecoder.Type] = type;

        coder.logValue("type", new Integer(type), coder.getPointer(), 16);
        coder.logValue("length", new Integer(length), coder.getPointer(), 16);

        coder.setPointer(coder.getPointer()+16);

        if (length == 0x3F)
            length = coder.readWord("lengthExtended", 4, false);

        return length;
    }

    static void decodeSetBackgroundColor(SWFDecoder coder)
    {
        coder.beginObject("background", "SetBackgroundColor");

        int type = coder.scanWord(2, false) >> 6;
        int length = coder.scanWord(2, false) & 0x3F;

        coder.context[SWFDecoder.Type] = type;

        coder.logValue("type", new Integer(type), coder.getPointer(), 16);
        coder.logValue("length", new Integer(length), coder.getPointer(), 16);

        coder.setPointer(coder.getPointer()+16);

        if (length == 0x3F)
            length = coder.readWord("lengthExtended", 4, false);

        decodeColor("color", coder);
        coder.endObject("SetBackgroundColor");
    }

    static void decodeShowFrame(SWFDecoder coder)
    {
        coder.beginObject("showFrame", "ShowFrame");
        decodeMovieObject(coder);
        coder.endObject("ShowFrame");
    }

    static void decodeDoAction(SWFDecoder coder)
    {
        coder.beginObject("doAction", "DoAction");

        int length = decodeMovieObject(coder);

        coder.beginObject("actions", "Array");

        while (length > 0)
            length -= decodeAction(coder);

       coder.endObject("Array");
       coder.endObject("DoAction");
    }

    static void decodeFontInfo(SWFDecoder coder)
    {
        coder.beginObject("fontInfo", "FontInfo");
        int length = decodeMovieObject(coder);

        coder.readWord("identifier", 2, false);
        int nameLength = coder.readWord("nameLength", 1, false);
        coder.readString("name", nameLength);
        coder.readBits("reserved", 2, false);
        coder.readBool("isSmall");
        coder.readBool("isShiftJIS");
        coder.readBool("isANSI");
        coder.readBool("isItalic");
        coder.readBool("isBold");
        boolean containsWideCodes = coder.readBool("hasWideCodes");

        int bytesRead = 3 + nameLength + 1;

        coder.beginObject("codes", "Array");

        while (bytesRead < length)
        {
            coder.readWord("code", containsWideCodes ? 2 : 1, false);
            bytesRead += (containsWideCodes) ? 2 : 1;
        }
        coder.endObject("Array");
        coder.endObject("FontInfo");
    }

    static void decodeJPEGEncodingTable(SWFDecoder coder)
    {
        coder.beginObject("JPEGEncodingTable", "JPEGEncodingTable");
        int length = decodeMovieObject(coder);
        coder.readBytes("encodingTable", length);
        coder.endObject("JPEGEncodingTable");
    }

    static void decodePlaceObject(SWFDecoder coder)
    {
        int lengthRead = coder.getPointer();

        coder.beginObject("placeObject", "PlaceObject");
        int length = decodeMovieObject(coder);
        coder.readWord("identifier", 2, false);
        coder.readWord("layer", 2, false);
        decodeCoordTransform("coordTransform", coder);

        lengthRead = (coder.getPointer() - lengthRead) >> 3;

        if (length > lengthRead)
            decodeColorTransform("colorTransform", coder);

        coder.endObject("PlaceObject");
    }

    static void decodeRemoveObject(SWFDecoder coder)
    {
        coder.beginObject("removeObject", "RemoveObject");
        decodeMovieObject(coder);
        coder.readWord("identifier", 2, false);
        coder.readWord("layer", 2, false);
        coder.endObject("RemoveObject");
    }

    static void decodeSoundStreamBlock(SWFDecoder coder)
    {
        coder.beginObject("soundStreamBlock", "SoundStreamBlock");
        int length = decodeMovieObject(coder);
        coder.readBytes("soundData", length); //
        coder.endObject("SoundStreamBlock");
    }

    static void decodeSoundStreamHead(SWFDecoder coder)
    {
        coder.beginObject("soundStreamHead", "SoundStreamHead");
        int length = decodeMovieObject(coder);
        coder.readBits("reserved", 4, false);
        coder.readBits("playbackRate", 2, false);
        coder.readBits("playbackSampleSize", 1, false);
        coder.readBits("playbackChannels", 1, false);
        int format = coder.readBits("format", 4, false);
        coder.readBits("streamRate", 2, false);
        coder.readBits("streamSampleSize", 1, false);
        coder.readBits("streamChannels", 1, false);
        coder.readWord("streamSampleCount", 2, false);

        if (length == 6 && format == 2) // MP3
            coder.readWord("latency", 2, true);

        coder.endObject("SoundStreamHead");
    }

    static void decodeStartSound(SWFDecoder coder)
    {
        coder.beginObject("startSound", "StartSound");
        decodeMovieObject(coder);
        decodeSound(coder);
        coder.endObject("StartSound");
    }

    static void decodeUnknownObject(SWFDecoder coder)
    {
        coder.beginObject("unknownObject", "UnknownObject");
        int length = decodeMovieObject(coder);
        coder.readBytes("data", length);
        coder.endObject("UnknownObject");
    }

    static int decodeActionObject(SWFDecoder coder)
    {
        int length = 0;

        if (coder.readWord("type", 1, false) >= 128)
            length = coder.readWord("length", 2, false);

        return length;
    }

    static void decodeGetUrl(SWFDecoder coder)
    {
        coder.beginObject("getUrl", "GetUrl");
        decodeActionObject(coder);
        coder.readString("url");
        coder.readString("target");
        coder.endObject("GetUrl");
    }

    static void decodeGotoFrame(SWFDecoder coder)
    {
        coder.beginObject("gotoFrame", "GotoFrame");
        decodeActionObject(coder);
        coder.readWord("frameNumber", 2, false);
        coder.endObject("GotoFrame");
    }

    static void decodeUnknownAction(SWFDecoder coder)
    {
        coder.beginObject("unknownAction", "UnknownAction");
        int length = decodeActionObject(coder);

        if (length > 0)
            coder.readBytes("data", length);

        coder.endObject("UnknownAction");
    }

    static void decodeTextRecord(SWFDecoder coder)
    {
        coder.beginObject("text", "Text");

        coder.readBits("type", 1, false); //
        coder.readBits("reserved", 3, false); //

        boolean _containsFont = coder.readBool("hasFont");
        boolean _containsColor = coder.readBool("hasColor");
        boolean _containsBaseline = coder.readBool("hasBaseline");
        boolean _containsIndent = coder.readBool("hasIndent");

        if (_containsFont)
            coder.readWord("identifier", 2, false);

        if (_containsColor)
            decodeColor("color", coder);

        if (_containsIndent)
            coder.readWord("indent", 2, true);
        if (_containsBaseline)
            coder.readWord("baseline", 2, true);
        if (_containsFont)
            coder.readWord("height", 2, false);

        int charCount = coder.readWord("characterCount", 1, false);

        coder.beginObject("characters", "Array");

        for (int i=0; i<charCount; i++)
            decodeCharacter(coder);

        coder.alignToByte();

        coder.endObject("Array");
        coder.endObject("Text");
    }

    static void decodeCharacter(SWFDecoder coder)
    {
        coder.beginObject("character", "Character");
        coder.readBits("glyphIndex", coder.context[SWFDecoder.NumberOfGlyphBits], false);
        coder.readBits("advance", coder.context[SWFDecoder.NumberOfAdvanceBits], true);
        coder.endObject("Character");
    }

    static int decodeDefineObject(SWFDecoder coder)
    {
        int length = decodeMovieObject(coder);

        coder.readWord("identifier", 2, false);

        return length;
    }

    static void decodeButtonRecord(SWFDecoder coder)
    {
        coder.beginObject("button", "Button");

        coder.readBits("reserved", 4, false);
        coder.readBool("isActive");
        coder.readBool("isDown");
        coder.readBool("isOver");
        coder.readBool("isUp");
        coder.readWord("identifier", 2, false);
        coder.readWord("layer", 2, false);
        decodeCoordTransform("coordTransform", coder);

        if (coder.context[SWFDecoder.Type] == DefineButton2)
            decodeColorTransform("colorTransform", coder);

        coder.endObject("Button");
    }

    static void decodeDefineButton(SWFDecoder coder)
    {
        coder.beginObject("defineButton", "DefineButton");

        int length = decodeDefineObject(coder)-2;
        int start = coder.getPointer();

        coder.beginObject("buttonRecords", "Array");

        while (coder.scanWord(1, false) != 0)
            decodeButtonRecord(coder);

        coder.readWord("endOfRecords", 1, false);

        coder.endObject("Array");

        length -= (coder.getPointer() - start) >>> 3;

        coder.beginObject("actions", "Array");

        while (length > 0)
            length -= decodeAction(coder);

        coder.endObject("Array");
        coder.endObject("DefineButton");
    }

    static void decodeDefineFont(SWFDecoder coder)
    {
        coder.beginObject("defineFont", "DefineFont");
        decodeDefineObject(coder);

        int shapeCount = coder.scanWord(2, false) / 2;

        int offsetStart = coder.getPointer();
        int[] offset = new int[shapeCount];

        coder.beginObject("offsetTable", "Array");

        for (int i=0; i<shapeCount; i++)
            offset[i] = coder.readWord("offset", 2, false);

        coder.endObject("Array");

        coder.beginObject("glyphs", "Array");

        for (int i=0; i<shapeCount; i++) {
            coder.setPointer(offsetStart+(offset[i]<<3));
            decodeShape(coder);
        }

        coder.endObject("Array");
        coder.endObject("DefineFont");
    }

    static void decodeDefineJPEGImage(SWFDecoder coder)
    {
        coder.beginObject("defineJPEGImage", "DefineJPEGImage");
        int length = decodeDefineObject(coder);
        coder.readBytes("image", length-2);
        coder.endObject("DefineJPEGImage");
    }

    static void decodeDefineShape(SWFDecoder coder)
    {
        coder.beginObject("defineShape", "DefineShape");
        decodeDefineObject(coder);

        decodeBounds("bounds", coder);

        int fillStyleCount = coder.readWord("fillStyleCount", 1, false);

        if (fillStyleCount > 0)
        {
            coder.beginObject("fillStyles", "Array");

            for (int i=0; i<fillStyleCount; i++)
            {
                switch (coder.scanWord(1, false))
                {
                    case Solid:
                        decodeSolidFill(coder);
                        break;
                    case Linear:
                        decodeGradientFill(coder);
                        break;
                    case Radial:
                        decodeGradientFill(coder);
                        break;
                    case Tiled:
                    case Unsmoothed_Tiled:
                        decodeBitmapFill(coder);
                        break;
                    case Clipped:
                    case Unsmoothed_Clipped:
                        decodeBitmapFill(coder);
                        break;
                }
            }
            coder.endObject("Array");
        }

        int lineStyleCount = coder.readWord("lineStyleCount", 1, false);

        if (lineStyleCount > 0)
        {
            coder.beginObject("lineStyles", "Array");

            for (int i=0; i<lineStyleCount; i++)
                decodeSolidLine(coder);

            coder.endObject("Array");
        }
        decodeShape(coder);

        coder.endObject("DefineShape");
    }

    static void decodeDefineSound(SWFDecoder coder)
    {
        coder.beginObject("defineSound", "DefineSound");
        int length = decodeDefineObject(coder);

        coder.readBits("format", 4, false);
        coder.readBits("playbackRate", 2, false);
        coder.readBits("playbackSampleSize", 1, false);
        coder.readBits("playbackChannels", 1, false);
        coder.readWord("sampleCount", 4, false);
        coder.readBytes("sound data", length-7);
        coder.endObject("DefineSound");
    }

    static void decodeDefineText(SWFDecoder coder)
    {
        coder.beginObject("defineText", "DefineText");
        decodeDefineObject(coder); // length

        decodeBounds("bounds", coder);

        // This code is used to get round a bug in Flash - sometimes 16,
        // 8-bit zeroes are written out before the transform. The root
        // cause in Flash is unknown but seems to be related to the
        // bounds not being set - all values are zero.

        boolean bytesAreZero = true;
        int start = coder.getPointer();

        for (int i=0; i<16; i++)
        {
            if (coder.scanWord(1, false) != 0)
                 bytesAreZero = false;

             coder.adjustPointer(8);
        }

        coder.setPointer(start);

        if (bytesAreZero)
        {
            for (int i=0; i<16; i++)
            {
                coder.scanWord(1, false);
                coder.adjustPointer(8);
            }
        }

        decodeCoordTransform("coordTransform", coder);

        coder.context[SWFDecoder.NumberOfGlyphBits] = coder.readWord("numberOfGlyphBits", 1, false);
        coder.context[SWFDecoder.NumberOfAdvanceBits] = coder.readWord("numberOfAdvanceBits", 1, false);

        coder.beginObject("textRecords", "Array");

        while (coder.scanBits(8, false) != 0)
            decodeTextRecord(coder);

        coder.readBits("endOfText", 8, false);
        coder.endObject("Array");

        coder.context[SWFDecoder.NumberOfGlyphBits] = 0;
        coder.context[SWFDecoder.NumberOfAdvanceBits] = 0;
        coder.endObject("DefineText");
    }

    static int decodeAction(SWFDecoder coder)
    {
        int type = coder.scanWord(1, false);
        int length = 0;
        int start = coder.getPointer() + 8;
        int next = start;

        if (type > 128)
        {
            coder.adjustPointer(8);
            length = coder.scanWord(2, false);
            start = coder.getPointer() + 16;
            next = start + (length << 3);
            coder.adjustPointer(-8);
        }

        boolean decoded = false;

        // Flash 1
        switch (type)
        {
            case End:
            case NextFrame:
            case PrevFrame:
            case Play:
            case Stop:
            case ToggleQuality:
            case StopSounds:
                decodeActionCode(coder);
                decoded = true;
                break;
            case GetUrl:
                decodeGetUrl(coder);
                decoded = true;
                break;
            case GotoFrame:
                decodeGotoFrame(coder);
                decoded = true;
                break;
            // Flash 2
            // Flash 3
            case GotoLabel:
                decodeGotoLabel(coder);
                decoded = true;
                break;
            case SetTarget:
                decodeSetTarget(coder);
                decoded = true;
                break;
            case WaitForFrame:
                decodeWaitForFrame(coder);
                decoded = true;
                break;
                // Flash 4
            case IntegerAdd:
            case Subtract:
            case IntegerEquals:
            case IntegerLess:
            case Multiply:
            case Divide:
            case And:
            case Or:
            case Not:
            case StringEquals:
            case StringLength:
            case StringExtract:
            case Pop:
            case ToInteger:
            case GetVariable:
            case SetVariable:
            case SetTarget2:
            case StringAdd:
            case GetProperty:
            case SetProperty:
            case CloneSprite:
            case RemoveSprite:
            case Trace:
            case StartDrag:
            case EndDrag:
            case StringLess:
            case RandomNumber:
            case MBStringLength:
            case CharToAscii:
            case AsciiToChar:
            case GetTime:
            case MBStringExtract:
            case MBCharToAscii:
            case MBAsciiToChar:
                decodeActionCode(coder);
                decoded = true;
                break;
            case Call:
                decodeCall(coder);
                decoded = true;
                break;
            case Push:
                decodePush(coder);
                decoded = true;
                break;
            case WaitForFrame2:
                decodeWaitForFrame2(coder);
                decoded = true;
                break;
            case Jump:
                decodeJump(coder);
                decoded = true;
                break;
            case If:
                decodeIf(coder);
                decoded = true;
                break;
            case GetUrl2:
                decodeGetUrl2(coder);
                decoded = true;
                break;
            case GotoFrame2:
                decodeGotoFrame2(coder);
                decoded = true;
                break;
                // Flash 5
            case Add:
            case Equals:
            case Less:
            case Modulo:
            case Enumerate:
            case Duplicate:
            case Swap:
            case ExecuteMethod:
            case NewMethod:
            case ExecuteFunction:
            case GetAttribute:
            case SetAttribute:
            case Increment:
            case Decrement:
            case LogicalShiftLeft:
            case LogicalShiftRight:
            case ArithmeticShiftRight:
            case BitwiseAnd:
            case BitwiseOr:
            case BitwiseXOr:
            case NamedObject:
            case NewArray:
            case NewObject:
            case DeleteVariable:
            case Delete:
            case Return:
            case ToNumber:
            case ToString:
            case GetType:
            case GetTarget:
            case NewVariable:
            case InitVariable:
                decodeActionCode(coder);
                decoded = true;
                break;
            case Table:
                decodeTable(coder);
                decoded = true;
                break;
            case RegisterCopy:
                decodeRegisterCopy(coder);
                decoded = true;
                break;
            case NewFunction:
                length = decodeNewFunction(coder);
                next = start + (length << 3);
                decoded = true;
                break;
            case With:
                decodeWith(coder);
                decoded = true;
                break;
                // Flash 6
            case InstanceOf:
            case EnumerateObject:
            case Greater:
            case StringGreater:
            case StrictEquals:
                decodeActionCode(coder);
                decoded = true;
                break;
                // Flash 7
            case Cast:
            case Extends:
            case Implements:
            case Throw:
                decodeActionCode(coder);
                decoded = true;
                break;
            case ExceptionHandler:
                decodeExceptionHandler(coder);
                decoded = true;
                break;
            case NewFunction2:
                decodeNewFunction2(coder);
                next = start + (length << 3);
                decoded = true;
                break;
        }

        if (decoded == false)
            decodeUnknownAction(coder);

        int delta = (next - coder.getPointer()) >> 3;

        if (delta < 0)
            coder.logError("ObjectOverflow", next, -delta);
        else if (delta > 0)
            coder.logError("ObjectUnderflow", next, delta);

        coder.setPointer(next);

        return (type >= 128) ? length + 3 : length + 1;
    }

    static void decodeClipEvent(SWFDecoder coder)
    {
        coder.beginObject("clipEvent", "ClipEvent");

        coder.readBool("keyUp");
        coder.readBool("keyDown");
        coder.readBool("mouseUp");
        coder.readBool("mouseDown");
        coder.readBool("mouseMove");
        coder.readBool("unload");
        coder.readBool("enterFrame");
        coder.readBool("load");

        if (coder.context[SWFDecoder.Version] > 5)
        {
            coder.readBool("dragOver");
            coder.readBool("rollOut");
            coder.readBool("rollOver");
            coder.readBool("releaseOutside");
            coder.readBool("release");
            coder.readBool("press");
            coder.readBool("initialize");
        }
        else
        {
            coder.readBits("reserved", 7, false);
        }
        coder.readBool("data");

        boolean keyPress = false;

        if (coder.context[SWFDecoder.Version] > 5)
        {
            coder.readBits("reserved", 5, false);
            coder.readBool("construct");
            keyPress = coder.readBool("keyPress");
            coder.readBool("dragOut");
            coder.readBits("reserved", 8, false);
        }

        int length = coder.readWord("offset", 4, false);

        if (keyPress) {
            coder.readWord("keyCode", 1, false);
            length -= 1;
        }

        coder.beginObject("actions", "Array");

        while (length > 0)
            length -= decodeAction(coder);

        coder.endObject("Array");
        coder.endObject("ClipEvent");
    }

    static void decodeMorphSolidLine(SWFDecoder coder)
    {
        coder.beginObject("morphSolidLine", "MorphSolidLine");
        coder.readWord("startWidth", 2, false);
        coder.readWord("endWidth", 2, false);
        decodeColor("startColor", coder);
        decodeColor("endColor", coder);
        coder.endObject("MorphSolidLine");
    }

    static void decodeMorphSolidFill(SWFDecoder coder)
    {
        coder.beginObject("morphSolidFill", "MorphSolidFill");
        coder.readWord("type", 1, false);
        decodeColor("startColor", coder);
        decodeColor("endColor", coder);
        coder.endObject("MorphSolidFill");
    }

    static void decodeMorphGradientFill(SWFDecoder coder)
    {
        coder.beginObject("morphGradientFill", "MorphGradientFill");
        coder.readWord("type", 1, false);
        decodeCoordTransform("startTransform", coder);
        decodeCoordTransform("endTransform", coder);

        int count = coder.readWord("gradientCount", 1, false);

        coder.beginObject("gradients", "Array");

        for (int i=0; i<count; i++)
            decodeMorphGradient(coder);

        coder.endObject("Array");
        coder.endObject("MorphGradientFill");
    }

    static void decodeMorphGradient(SWFDecoder coder)
    {
        coder.beginObject("morphGradient", "MorphGradient");
        coder.readWord("startRatio", 1, false);
        decodeColor("startColor", coder);
        coder.readWord("endRatio", 1, false);
        decodeColor("endColor", coder);
        coder.endObject("MorphGradient");
    }

    static void decodeMorphBitmapFill(SWFDecoder coder)
    {
        coder.beginObject("morphBitmapFill", "MorphBitmapFill");
        coder.readWord("type", 1, false); //
        coder.readWord("identifier", 2, false); //
        decodeCoordTransform("startTransform", coder);
        decodeCoordTransform("endTransform", coder);
        coder.endObject("MorphBitmapFill");
    }

    static void decodeButtonColorTransform(SWFDecoder coder)
    {
        coder.beginObject("buttonColorTransform", "ButtonColorTransform");
        decodeMovieObject(coder);
        coder.readWord("identifier", 2, false);
        decodeColorTransform("colorTransform", coder);
        coder.endObject("ButtonColorTransform");
    }

    static void decodeButtonSound(SWFDecoder coder)
    {
        coder.beginObject("buttonSound", "ButtonSound");

        int length = decodeMovieObject(coder);
        int start = coder.getPointer();

        coder.readWord("identifier", 2, false);

        for (int i=0; i<4; i++)
        {
            if (coder.scanWord(2, false) > 0)
                decodeSound(coder);
            else
                coder.readWord("none", 2, false);

            if (((coder.getPointer() - start) >>> 3) == length)
                break;
        }
        coder.endObject("ButtonSound");
    }

    static void decodeEnableDebugger(SWFDecoder coder)
    {
        coder.beginObject("enableDebugger", "EnableDebugger");
        decodeMovieObject(coder);
        coder.readString("password");
        coder.endObject("EnableDebugger");
    }

    static void decodeEnableDebugger2(SWFDecoder coder)
    {
        coder.beginObject("EnableDebugger2", "EnableDebugger2");
        decodeMovieObject(coder);
        coder.readWord("reserved", 2, false);
        coder.readString("password");
        coder.endObject("EnableDebugger2");
    }

    static void decodeExport(SWFDecoder coder)
    {
        coder.beginObject("export", "Export");
        decodeMovieObject(coder);

        int count = coder.readWord("count", 2, false);

        coder.beginObject("objects", "Array");
        for (int i=0; i<count; i++)
        {
            coder.beginObject("exports", "Object");
            coder.readWord("identifier", 2, false);
            coder.readString("name");
            coder.endObject("Object");
        }
        coder.endObject("Array");
        coder.endObject("Export");
    }

    static void decodeFontInfo2(SWFDecoder coder)
    {
        coder.beginObject("fontInfo2", "FontInfo2");
        int length = decodeMovieObject(coder);

        coder.readWord("identifier", 2, false);
        int nameLength = coder.readWord("nameLength", 1, false);
        coder.readString("name", nameLength);
        coder.readBits("reserved", 2, false);
        coder.readBool("isSmall");
        coder.readBits("encoding", 2, false);
        coder.readBool("isItalic");
        coder.readBool("isBold");
        coder.readBool("hasWideCodes");
        coder.readWord("language", 1, false);

        int bytesRead = 4 + nameLength + 1;

        coder.beginObject("codes", "Array");

        while (bytesRead < length)
        {
            coder.readWord("code", 2, false);
            bytesRead += 2;
        }
        coder.endObject("Array");
        coder.endObject("FontInfo2");
    }

    static void decodeFrameLabel(SWFDecoder coder)
    {
        coder.beginObject("frameLabel", "FrameLabel");
        int length = decodeMovieObject(coder);

        int start = coder.getPointer();
        int strlen = 0;

        for (strlen=0; coder.scanWord(1, false) != 0; coder.adjustPointer(8), strlen++);

        coder.setPointer(start);
        coder.readString("label", strlen++);
        coder.adjustPointer(8);

        if (coder.context[SWFDecoder.Version] > 5 && strlen < length)
            coder.readWord("anchor", 1, false);

        coder.endObject("FrameLabel");
    }

    static void decodeFree(SWFDecoder coder)
    {
        coder.beginObject("free", "Free");
        decodeMovieObject(coder);
        coder.readWord("identifier", 2, false);
        coder.endObject("Free");
    }

    static void decodeImport(SWFDecoder coder)
    {
        coder.beginObject("import", "Import");
        decodeMovieObject(coder);
        coder.readString("url"); // url

        int count = coder.readWord("count", 2, false);

        coder.beginObject("objects", "Array");
        for (int i=0; i<count; i++)
        {
            coder.beginObject("object", "Object");
            coder.readWord("identifier", 2, false);
            coder.readString("name");
            coder.endObject("Object");
        }
        coder.endObject("Array");
        coder.endObject("Import");
    }

    static void decodeInitialize(SWFDecoder coder)
    {
        coder.beginObject("initialize", "Initialize");
        int length = decodeMovieObject(coder)-2;
        coder.readWord("identifier", 2, false);

        coder.beginObject("actions", "Array");

        while (length > 0)
            length -= decodeAction(coder);

        coder.endObject("Array");
        coder.endObject("Initialize");
    }

    static void decodePathsArePostscript(SWFDecoder coder)
    {
        coder.beginObject("pathsArePostscript", "PathsArePostscript");
        decodeMovieObject(coder);
        coder.endObject("PathsArePostscript");
    }

    static void decodePlaceObject2(SWFDecoder coder)
    {
        coder.context[SWFDecoder.TransparentColors] = 1;

        coder.beginObject("placeObject2", "PlaceObject2");

        decodeMovieObject(coder);

        boolean containsClipEvents = coder.readBool("hasClipEvents");
        boolean containsClippingDepth = coder.readBool("hasClippingDepth");
        boolean containsName = coder.readBool("hasName");
        boolean containsRatio = coder.readBool("hasRatio");
        boolean containsColorTransform = coder.readBool("hasColorTransform");
        boolean containsTransform = coder.readBool("hasCoordTransform");

        int place = coder.readBits("placeType", 2, false);

        coder.readWord("layer", 2, false);

        if (place == 2 || place == 3)
            coder.readWord("identifier", 2, false);

        if (containsTransform)
            decodeCoordTransform("coordTransform", coder);

        if (containsColorTransform)
            decodeColorTransform("colorTransform", coder);

        if (containsRatio)
            coder.readWord("ratio", 2, false);

        if (containsName)
            coder.readString("name");

        if (containsClippingDepth)
            coder.readWord("clippingDepth", 2, false);

        if (containsClipEvents)
        {
            int eventSize = coder.context[SWFDecoder.Version] > 5 ? 4 : 2;

            coder.readWord("reserved", 2, false);
            coder.readWord("eventMask", eventSize, false);

            coder.beginObject("clipEvents", "Array");

            while (coder.scanWord(eventSize, false) != 0)
                decodeClipEvent(coder);

            coder.readWord("eventEnd", eventSize, false);
            coder.endObject("Array");
        }
        coder.endObject("PlaceObject2");
        coder.context[SWFDecoder.TransparentColors] = 0;
    }

    static void decodePointer(SWFDecoder coder)
    {
        coder.beginObject("pointer", "Pointer");
        decodeMovieObject(coder);
        coder.readWord("pointer", 4, false);
        coder.endObject("Pointer");
    }

    static void decodeProtect(SWFDecoder coder)
    {
        coder.beginObject("protect", "Protect");
        int length = decodeMovieObject(coder);

        if ((coder.context[SWFDecoder.Version] > 4) && (length > 0))
            coder.readString("password", length);

        coder.endObject("Protect");
    }

    static void decodeQuicktimeMovie(SWFDecoder coder)
    {
        coder.beginObject("quicktimeMovie", "QuicktimeMovie");
        decodeMovieObject(coder);
        coder.readString("name");
        coder.endObject("QuicktimeMovie");
    }

    static void decodeRemoveObject2(SWFDecoder coder)
    {
        coder.beginObject("removeObject2", "RemoveObject2");
        decodeMovieObject(coder);
        coder.readWord("layer", 2, false);
        coder.endObject("RemoveObject2");
    }

    static void decodeSerialNumber(SWFDecoder coder)
    {
        coder.beginObject("serialNumber", "SerialNumber");
        decodeMovieObject(coder);
        coder.readString("serialNumber");
        coder.endObject("SerialNumber");
    }

    static void decodeSoundStreamHead2(SWFDecoder coder)
    {
        coder.beginObject("soundStreamHead2", "SoundStreamHead2");
        int length = decodeMovieObject(coder);
        coder.readBits("reserved", 4, false);
        coder.readBits("playbackRate", 2, false);
        coder.readBits("playbackSampleSize", 1, false);
        coder.readBits("playbackChannels", 1, false);
        int format = coder.readBits("format", 4, false);
        coder.readBits("streamRate", 2, false);
        coder.readBits("streamSampleSize", 1, false);
        coder.readBits("streamChannels", 1, false);
        coder.readWord("streamSampleCount", 2, false);

        if (length == 6 && format == 2) // MP3
            coder.readWord("latency", 2, true);

        coder.endObject("SoundStreamHead2");
    }

    static void decodeTabOrder(SWFDecoder coder)
    {
        coder.beginObject("tabOrder", "TabOrder");
        decodeMovieObject(coder);
        coder.readWord("layer", 2, false);
        coder.readWord("index", 2, false);
        coder.endObject("TabOrder");
    }

    static void decodeLimitScript(SWFDecoder coder)
    {
        coder.beginObject("limitScript", "LimitScript");
        decodeMovieObject(coder);
        coder.readWord("depth", 2, false);
        coder.readWord("timeout", 2, false);
        coder.endObject("LimitScript");
    }

    static void decodeVideoFrame(SWFDecoder coder)
    {
        coder.beginObject("videoFrame", "VideoFrame");
        int length = decodeMovieObject(coder);

        coder.readWord("identifier", 2, false);
        coder.readWord("frameNumber", 2, false);
        coder.readBytes("video data", length-4);
        coder.endObject("VideoFrame");
    }

    static void decodeActionCode(SWFDecoder coder)
    {
        String name = null;

        coder.beginObject("action", "Action");

        switch (coder.readWord("type", 1, false))
        {
            case End:
                name = "End";
                break;
            case NextFrame:
                name = "NextFrame";
                break;
            case PrevFrame:
                name = "PrevFrame";
                break;
            case Play:
                name = "Play";
                break;
            case Stop:
                name = "Stop";
                break;
            case ToggleQuality:
                name = "ToggleQuality";
                break;
            case StopSounds:
                name = "StopSounds";
                break;
            case IntegerAdd:
                name = "IntegerAdd";
                break;
            case Subtract:
                name = "Subtract";
                break;
            case Multiply:
                name = "Multiply";
                break;
            case Divide:
                name = "Divide";
                break;
            case IntegerEquals:
                name = "IntegerEquals";
                break;
            case IntegerLess:
                name = "IntegerLess";
                break;
            case Less:
                name = "Less";
                break;
            case And:
                name = "And";
                break;
            case Or:
                name = "Or";
                break;
            case Not:
                name = "Not";
                break;
            case StringEquals:
                name = "StringEquals";
                break;
            case StringLength:
                name = "StringLength";
                break;
            case StringExtract:
                name = "StringExtract";
                break;
            case Pop:
                name = "Pop";
                break;
            case ToInteger:
                name = "ToInteger";
                break;
            case GetVariable:
                name = "GetVariable";
                break;
            case SetVariable:
                name = "SetVariable";
                break;
            case SetTarget2:
                name = "SetTarget2";
                break;
            case StringAdd:
                name = "StringAdd";
                break;
            case GetProperty:
                name = "GetProperty";
                break;
            case SetProperty:
                name = "SetProperty";
                break;
            case CloneSprite:
                name = "CloneSprite";
                break;
            case RemoveSprite:
                name = "RemoveSprite";
                break;
            case Trace:
                name = "Trace";break;
            case StartDrag:
                name = "StartDrag";
                break;
            case EndDrag:
                name = "EndDrag";
                break;
            case StringLess:
                name = "StringLess";
                break;
            case RandomNumber:
                name = "RandomNumber";
                break;
            case MBStringLength:
                name = "MBStringLength";
                break;
            case CharToAscii:
                name = "CharToAscii";
                break;
            case AsciiToChar:
                name = "AsciiToChar";
                break;
            case GetTime:
                name = "GetTime";
                break;
            case MBStringExtract:
                name = "MBStringExtract";
                break;
            case MBCharToAscii:
                name = "MBCharToAscii";
                break;
            case MBAsciiToChar:
                name = "MBAsciiToChar";
                break;
            case DeleteVariable:
                name = "DeleteVariable";
                break;
            case Delete:
                name = "Delete";
                break;
            case InitVariable:
                name = "InitVariable";
                break;
            case ExecuteFunction:
                name = "ExecuteFunction";
                break;
            case Return:
                name = "Return";
                break;
            case Modulo:
                name = "Modulo";
                break;
            case NamedObject:
                name = "NamedObject";
                break;
            case NewVariable:
                name = "NewVariable";
                break;
            case NewArray:
                name = "NewArray";
                break;
            case NewObject:
                name = "NewObject";
                break;
            case GetType:
                name = "GetType";
                break;
            case GetTarget:
                name = "GetTarget";
                break;
            case Enumerate:
                name = "Enumerate";
                break;
            case Add:
                name = "Add";
                break;
            case Equals:
                name = "Equals";
                break;
            case ToNumber:
                name = "ToNumber";
                break;
            case ToString:
                name = "ToString";
                break;
            case Duplicate:
                name = "Duplicate";
                break;
            case Swap:
                name = "Swap";
                break;
            case GetAttribute:
                name = "GetAttribute";
                break;
            case SetAttribute:
                name = "SetAttribute";
                break;
            case Increment:
                name = "Increment";
                break;
            case Decrement:
                name = "Decrement";
                break;
            case ExecuteMethod:
                name = "ExecuteMethod";
                break;
            case NewMethod:
                name = "NewMethod";
                break;
            case BitwiseAnd:
                name = "BitwiseAnd";
                break;
            case BitwiseOr:
                name = "BitwiseOr";
                break;
            case BitwiseXOr:
                name = "BitwiseXOr";
                break;
            case LogicalShiftLeft:
                name = "LogicalShiftLeft";
                break;
            case LogicalShiftRight:
                name = "LogicalShiftRight";
                break;
            case ArithmeticShiftRight:
                name = "ArithmeticShiftRight";
                break;
            case InstanceOf:
                name = "InstanceOf";
                break;
            case EnumerateObject:
                name = "EnumerateObject";
                break;
            case Greater:
                name = "GreaterThan";
                break;
            case StringGreater:
                name = "StringGreater";
                break;
            case StrictEquals:
                name = "StrictEquals";
                break;
            case Cast:
                name = "Cast";
                break;
            case Implements:
                name = "Implements";
                break;
            case Throw:
                name = "Throw";
                break;
            case Extends:
                name = "Extends";
                break;
            default:
                name = "Action";
                break;
        }
        coder.endObject(name);
    }

    static void decodeCall(SWFDecoder coder)
    {
        coder.beginObject("call", "Call");
        decodeActionObject(coder);
        coder.endObject("Call");
    }

    static void decodeExceptionHandler(SWFDecoder coder)
    {
        coder.beginObject("exceptionHandler", "ExceptionHandler");
        decodeActionObject(coder);

        coder.readBits("reserved", 5, false);
        boolean containsVariable = coder.readBool("hasVariable");
        boolean containsCatch = coder.readBool("hasCatch");
        boolean containsFinal = coder.readBool("hasFinal");

        int tryLength = coder.readWord("tryLength", 2, false);
        int catchLength = coder.readWord("catchLength", 2, false);
        int finalLength = coder.readWord("finalLength", 2, false);

        if (containsVariable) {
            coder.readString("variable");
        }
        else {
            coder.readWord("register", 1, false);
        }

        coder.beginObject("tryActions", "Array");

        while (tryLength > 0)
            tryLength -= decodeAction(coder);

        coder.endObject("Array");

        if (containsCatch)
        {
            coder.beginObject("catchActions", "Array");

            while (catchLength > 0)
                catchLength -= decodeAction(coder);
            coder.endObject("Array");
        }

        if (containsFinal)
        {
            coder.beginObject("finalActions", "Array");

            while (finalLength > 0)
                finalLength -= decodeAction(coder);

            coder.endObject("Array");
        }
        coder.endObject("ExceptionHandler");
    }

    static void decodeGetUrl2(SWFDecoder coder)
    {
        coder.beginObject("getUrl2", "GetUrl2");
        decodeActionObject(coder);
        coder.readWord("requestType", 1, false);
        coder.endObject("GetUrl2");
    }

    static void decodeGotoFrame2(SWFDecoder coder)
    {
        coder.beginObject("gotoFrame2", "GotoFrame2");
        decodeActionObject(coder);
        coder.readBits("unused", 6, false);
        boolean containsOffset = coder.readBool("hasFrameOffset");
        coder.readBool("playFrame");

        if (containsOffset)
            coder.readWord("frameOffset", 2, false);

        coder.endObject("GotoFrame2");
    }

    static void decodeGotoLabel(SWFDecoder coder)
    {
        coder.beginObject("gotoLabel", "GotoLabel");
        decodeActionObject(coder);
        coder.readString("label");
        coder.endObject("GotoLabel");
    }

    static void decodeIf(SWFDecoder coder)
    {
        coder.beginObject("if", "If");
        decodeActionObject(coder);
        coder.readWord("offset", 2, true);
        coder.endObject("If");
    }

    static void decodeJump(SWFDecoder coder)
    {
        coder.beginObject("jump", "Jump");
        decodeActionObject(coder);
        coder.readWord("offset", 2, true);
        coder.endObject("Jump");
    }

    static int decodeNewFunction(SWFDecoder coder)
    {
        coder.beginObject("newFunction", "NewFunction");
        int length = decodeActionObject(coder);
        coder.readString("name");

        int argumentCount = coder.readWord("argumentCount", 2, false);

        if (argumentCount > 0)
        {
            coder.beginObject("arguments", "Array");

            for (int i=argumentCount; i>0; i--)
                coder.readString("arg");

            coder.endObject("Array");
        }

        coder.readWord("actionsLength", 2, false);

        coder.endObject("NewFunction");

        return length;
    }

    static int decodeNewFunction2(SWFDecoder coder)
    {
        coder.beginObject("newFunction2", "NewFunction2");
        int length = decodeActionObject(coder);
        coder.readString("name");

        int argumentCount = coder.readWord("argumentCount", 2, false);
        coder.readWord("registerCount", 1, false);
        coder.readBits("optimizations", 16, false);

        coder.beginObject("registerVariables", "Array");

        for (int i=0; i<argumentCount; i++)
            decodeRegisterVariable(coder);

        coder.endObject("Array");

        coder.readWord("actionsLength", 2, false);

        coder.endObject("NewFunction2");

        return length;
    }

    static void decodePush(SWFDecoder coder)
    {
        coder.beginObject("push", "Push");
        int length = decodeActionObject(coder);

        coder.beginObject("values", "Array");

        while (length > 0)
        {
            int dataType = coder.scanWord(1, false);

            switch (dataType)
            {
                case 0:
                    length -= decodeString(coder);
                    break;
                case 1:
                    length -= decodeProperty(coder);
                    break;
                case 2:
                    length -= decodeNull(coder);
                    break;
                case 3:
                    length -= decodeVoid(coder);
                    break;
                case 4:
                    length -= decodeRegisterIndex(coder);
                    break;
                case 5:
                    length -= decodeBoolean(coder);
                    break;
                case 6:
                    length -= decodeDouble(coder);
                    break;
                case 7:
                    length -= decodeInteger(coder);
                    break;
                case 8:
                    length -= decodeTableIndexA(coder);
                    break;
                case 9:
                    length -= decodeTableIndexB(coder);
                    break;
            }
        }
        coder.endObject("Array");
        coder.endObject("Push");
    }

    static int decodeString(SWFDecoder coder)
    {
        coder.beginObject("string", "String");
        coder.readWord("type", 1, false);
        int length = 2 + coder.readString("value");
        coder.endObject("String");

        return length;
    }

    static int decodeProperty(SWFDecoder coder)
    {
        coder.beginObject("property", "Property");
        coder.readWord("type", 1, false); // type
        coder.readWord("value", 4, false); // property
        coder.endObject("Property");

        return 5;
    }

    static int decodeNull(SWFDecoder coder)
    {
        coder.beginObject("null", "Null");
        coder.readWord("type", 1, false); // type
        coder.endObject("Null");

        return 1;
    }

    static int decodeVoid(SWFDecoder coder)
    {
        coder.beginObject("void", "Void");
        coder.readWord("type", 1, false); // type
        coder.endObject("Void");

        return 1;
    }

    static int decodeRegisterIndex(SWFDecoder coder)
    {
        coder.beginObject("registerIndex", "RegisterIndex");
        coder.readWord("type", 1, false); // type
        coder.readWord("value", 1, false); // index
        coder.endObject("RegisterIndex");

        return 2;
    }

    static int decodeBoolean(SWFDecoder coder)
    {
        coder.beginObject("boolean", "Boolean");
        coder.readWord("type", 1, false); // type
        coder.readWord("value", 1, false); // value
        coder.endObject("Boolean");

        return 2;
    }

    static int decodeDouble(SWFDecoder coder)
    {
        coder.beginObject("double", "Double");
        coder.readWord("type", 1, false); // type
        coder.readDouble("value"); // value
        coder.endObject("Double");

        return 9;
    }

    static int decodeInteger(SWFDecoder coder)
    {
        coder.beginObject("integer", "Integer");
        coder.readWord("type", 1, false); // type
        coder.readWord("value", 4, true); // value
        coder.endObject("Integer");

        return 5;
    }

    static int decodeTableIndexA(SWFDecoder coder)
    {
        coder.beginObject("tableIndex", "TableIndex");
        coder.readWord("type", 1, false); // type
        coder.readWord("value", 1, true); // value
        coder.endObject("TableIndex");

        return 2;
    }

    static int decodeTableIndexB(SWFDecoder coder)
    {
        coder.beginObject("tableIndex", "TableIndex");
        coder.readWord("type", 1, false);
        coder.readWord("value", 2, true);
        coder.endObject("TableIndex");

        return 3;
    }

    static void decodeRegisterVariable(SWFDecoder coder)
    {
        coder.beginObject("registerVariable", "RegisterVariable");
        coder.readWord("register", 1, false);
        coder.readString("name");
        coder.endObject("RegisterVariable");
    }

    static void decodeRegisterCopy(SWFDecoder coder)
    {
        coder.beginObject("registerCopy", "RegisterCopy");
        decodeActionObject(coder);
        coder.readWord("register", 1, false);
        coder.endObject("RegisterCopy");
    }

    static void decodeSetTarget(SWFDecoder coder)
    {
        coder.beginObject("setTarget", "SetTarget");
        decodeActionObject(coder);
        coder.readString("target");
        coder.endObject("SetTarget");
    }

    static void decodeTable(SWFDecoder coder)
    {
        coder.beginObject("table", "Table");
        decodeActionObject(coder);

        int attributeCount = coder.readWord("variableCount", 2, false);

        if (attributeCount > 0)
        {
            coder.beginObject("strings", "Array");

            for (int i=0; i<attributeCount; i++)
                coder.readString("value");

            coder.endObject("Array");
        }
        coder.endObject("Table");
    }

    static void decodeWaitForFrame(SWFDecoder coder)
    {
        coder.beginObject("waitForFrame", "WaitForFrame");
        decodeActionObject(coder);
        coder.readWord("frameNumber", 2, false);
        coder.readWord("actionCount", 1, false);
        coder.endObject("WaitForFrame");
    }

    static void decodeWaitForFrame2(SWFDecoder coder)
    {
        coder.beginObject("waitForFrame2", "WaitForFrame2");
        decodeActionObject(coder);
        coder.readWord("actionCount", 1, false);
        coder.endObject("WaitForFrame2");
    }

    static void decodeWith(SWFDecoder coder)
    {
        coder.beginObject("with", "With");
        decodeActionObject(coder);
        coder.readWord("actionsLength", 1, false);
        coder.endObject("With");
    }

    static int decodeButtonEvent(SWFDecoder coder, int bytesLeft)
    {
        coder.beginObject("buttonEvent", "ButtonEvent");

        int offsetToEnd = coder.readWord("offsetToEnd", 2, false);

        int length = (offsetToEnd != 0) ? offsetToEnd-2 : bytesLeft-4;

        coder.readBool("menuDragOver");
        coder.readBool("releaseOut");
        coder.readBool("dragOver");
        coder.readBool("dragOut");
        coder.readBool("release");
        coder.readBool("press");
        coder.readBool("rollOut");
        coder.readBool("rollOver");
        coder.readBits("keyCode", 7, false);
        coder.readBool("menuDragOut");

        coder.beginObject("actions", "Array");

        while (length > 0)
            length -= decodeAction(coder);

        coder.endObject("Array");
        coder.endObject("ButtonEvent");

        return offsetToEnd;
    }

    static void decodeDefineButton2(SWFDecoder coder)
    {
        coder.beginObject("defineButton2", "DefineButton2");
        int length = decodeDefineObject(coder) - 2;
        int start = coder.getPointer();

        coder.context[SWFDecoder.TransparentColors] = 1;

        coder.readWord("buttonType", 1, false);
        int actionsOffset = coder.readWord("actionsOffset", 2, false);

        coder.beginObject("buttonRecords", "Array");

        while (coder.scanWord(1, false) != 0)
            decodeButtonRecord(coder);

        coder.readWord("endOfRecords", 1, false);
        coder.endObject("Array");

        if (actionsOffset != 0)
        {
            coder.beginObject("buttonEvents", "Array");

            do {
                actionsOffset = decodeButtonEvent(coder, length - ((coder.getPointer() - start) >>> 3));
            } while (actionsOffset != 0);

            coder.endObject("Array");
        }
        coder.context[SWFDecoder.TransparentColors] = 0;
        coder.endObject("DefineButton2");
    }

    static void decodeDefineFont2(SWFDecoder coder)
    {
        coder.beginObject("defineFont2", "DefineFont2");
        int length = decodeDefineObject(coder);
        int start = coder.getPointer() - 16;

        boolean containsLayout = coder.readBool("hasLayout");

        coder.readBool("isShiftJIS");
        coder.readBool("isSmall");
        coder.readBool("isANSI");

        boolean containsWideOffsets = coder.readBool("hasWideOffsets");
        boolean containsWideCodes = coder.readBool("hasWideCodes");

        coder.context[SWFDecoder.WideCodes] = containsWideCodes ? 1 : 0;

        coder.readBool("isItalic");
        coder.readBool("isBold");
        coder.readBits("language", 8, false);
        int nameLength = coder.readWord("nameLength", 1, false);
        coder.readString("name", nameLength);
        int glyphCount = coder.readWord("glyphCount", 2, false);

        int offsetStart = coder.getPointer();
        int[] offset = new int[glyphCount];

        coder.beginObject("offsets", "Array");

        for (int i=0; i<glyphCount; i++)
            offset[i] = coder.readWord("offset", containsWideOffsets ? 4 : 2, false);

        coder.endObject("Array");

        int codeOffset = coder.readWord("codeOffset", (containsWideOffsets) ? 4 : 2, false); // codeOffset

        coder.beginObject("glyphs", "Array");

        for (int i=0; i<glyphCount; i++) {
            coder.setPointer(offsetStart+(offset[i]<<3));
            decodeShape(coder);
        }

        coder.endObject("Array");

        coder.beginObject("codes", "Array");

        coder.setPointer(offsetStart+(codeOffset<<3));

        for (int i=0; i<glyphCount; i++)
            coder.readWord("code", containsWideCodes ? 2 : 1, false);

        coder.endObject("Array");

        if (containsLayout)
        {
            coder.readWord("ascent", 2, true);
            coder.readWord("descent", 2, true);
            coder.readWord("leading", 2, true);

            coder.beginObject("advances", "Array");

            for (int i=0; i<glyphCount; i++)
                coder.readWord("advance", 2, true);

            coder.endObject("Array");


            if (((coder.getPointer() - start) >> 3) < length)
            {
                coder.beginObject("bounds", "Array");

                for (int i=0; i<glyphCount; i++)
                    decodeBounds("bounds", coder);

                coder.endObject("Array");

                int kerningCount = coder.readWord("kerningCount", 2, false);

                coder.beginObject("kernings", "Array");

                for (int i=0; i<kerningCount; i++)
                    decodeKerning(coder);

                coder.endObject("Array");
            }
        }

        coder.context[SWFDecoder.WideCodes] = 0;
        coder.endObject("DefineFont2");
    }

    static void decodeKerning(SWFDecoder coder)
    {
        int numberOfBytes = (coder.context[SWFDecoder.WideCodes] != 0) ? 2 : 1;

        coder.beginObject("kerning", "Kerning");
        coder.readWord("leftGlyphIndex", numberOfBytes, false);
        coder.readWord("rightGlyphIndex", numberOfBytes, false);
        coder.readWord("adjustment", 2, true);
        coder.endObject("Kerning");
    }

    static void decodeDefineImage(SWFDecoder coder)
    {
        coder.beginObject("defineImage", "DefineImage");
        int length = decodeDefineObject(coder);
        int format = coder.readWord("format", 1, false);

        coder.readWord("width", 2, false);
        coder.readWord("height", 2, false);

        if (format == 3)
        {
            coder.readWord("tableSize", 1, false);
            coder.readBytes("compressedData", length-8);
        }
        else
        {
            coder.readBytes("compressedData", length-7);
        }
        coder.endObject("DefineImage");
    }

    static void decodeDefineImage2(SWFDecoder coder)
    {
        coder.beginObject("defineImage2", "DefineImage2");
        int length = decodeDefineObject(coder);
        int format = coder.readWord("format", 1, false);

        coder.readWord("width", 2, false);
        coder.readWord("height", 2, false);

        if (format == 3)
        {
            coder.readWord("tableSize", 1, false);
            coder.readBytes("compressedData", length-8);
        }
        else
        {
            coder.readBytes("compressedData", length-7);
        }
        coder.endObject("DefineImage2");
    }

    static void decodeDefineJPEGImage2(SWFDecoder coder)
    {
        coder.beginObject("defineJPEGImage2", "DefineJPEGImage2");
        int length = decodeDefineObject(coder);
        coder.readBytes("image", length-2);
        coder.endObject("DefineJPEGImage2");
    }

    static void decodeDefineJPEGImage3(SWFDecoder coder)
    {
        coder.beginObject("defineJPEGImage3", "DefineJPEGImage3");
        int length = decodeDefineObject(coder);
        int offset = coder.readWord("offset", 4, false);
        coder.readBytes("image", offset);
        coder.readBytes("alpha", length-offset-6);
        coder.endObject("DefineJPEGImage3");
    }

    static void decodeDefineMorphShape(SWFDecoder coder)
    {
        coder.beginObject("defineMorphShape", "DefineMorphShape");
        decodeDefineObject(coder); // length

        coder.context[SWFDecoder.TransparentColors] = 1;
        coder.context[SWFDecoder.ArrayCountExtended] = 1;

        decodeBounds("startBounds", coder);
        decodeBounds("endBounds", coder);

        coder.readWord("offset", 4, false); // offset

        int fillStyleCount = coder.readWord("fillStyleCount", 1, false);

        if (coder.context[SWFDecoder.ArrayCountExtended] != 0 && fillStyleCount == 0xFF)
            fillStyleCount = coder.readWord("fillStyleCountExtended", 2, false);

        coder.beginObject("fillStyles", "Array");

        for (int i=0; i<fillStyleCount; i++)
        {
            switch (coder.scanWord(1, false))
            {
                case Solid:
                    decodeMorphSolidFill(coder);
                    break;
                case Linear:
                    decodeMorphGradientFill(coder);
                    break;
                case Radial:
                    decodeMorphGradientFill(coder);
                    break;
                case Tiled:
                case Unsmoothed_Tiled:
                    decodeMorphBitmapFill(coder);
                    break;
                case Clipped:
                case Unsmoothed_Clipped:
                    decodeMorphBitmapFill(coder);
                    break;
            }
        }
        coder.endObject("Array");

        int lineStyleCount = coder.readWord("lineStyleCount", 1, false);

        if (coder.context[SWFDecoder.ArrayCountExtended] != 0 && lineStyleCount == 0xFF)
            lineStyleCount = coder.readWord("lineStyleCountExtended", 2, false);

        coder.beginObject("lineStyles", "Array");

        for (int i=0; i<lineStyleCount; i++)
            decodeMorphSolidLine(coder);

        coder.endObject("Array");

        decodeShape(coder); // startShape
        decodeShape(coder); // endShape

        coder.context[SWFDecoder.TransparentColors] = 0;
        coder.context[SWFDecoder.ArrayCountExtended] = 0;

        coder.endObject("DefineMorphShape");
    }

    static void decodeDefineMovieClip(SWFDecoder coder)
    {
        coder.beginObject("defineMovieClip", "DefineMovieClip");
        decodeDefineObject(coder);

        coder.readWord("frameCount", 2, false);

        coder.beginObject("objects", "Array");

        while (decodeObject(coder));

        coder.endObject("Array");

        coder.endObject("DefineMovieClip");
    }

    static void decodeDefineShape2(SWFDecoder coder)
    {
        coder.beginObject("defineShape2", "DefineShape2");
        decodeDefineObject(coder);

        decodeBounds("bounds", coder);

        int fillStyleCount = coder.readWord("fillStyleCount", 1, false);

        if (fillStyleCount > 0)
        {
            if (fillStyleCount == 0xFF)
                fillStyleCount = coder.readWord("fillStyleCountExtended", 2, false);

            coder.beginObject("fillStyles", "Array");

            for (int i=0; i<fillStyleCount; i++)
            {
                switch (coder.scanWord(1, false))
                {
                    case Solid:
                        decodeSolidFill(coder);
                        break;
                    case Linear:
                        decodeGradientFill(coder);
                        break;
                    case Radial:
                        decodeGradientFill(coder);
                        break;
                    case Tiled:
                    case Unsmoothed_Tiled:
                        decodeBitmapFill(coder);
                        break;
                    case Clipped:
                    case Unsmoothed_Clipped:
                        decodeBitmapFill(coder);
                        break;
                }
            }

            coder.endObject("Array");
        }

        int lineStyleCount = coder.readWord("lineStyleCount", 1, false);

        if (lineStyleCount > 0)
        {
            if (lineStyleCount == 0xFF)
                lineStyleCount = coder.readWord("lineStyleCountExtended", 2, false);

            coder.beginObject("lineStyles", "Array");

            for (int i=0; i<lineStyleCount; i++)
                decodeSolidLine(coder);

            coder.endObject("Array");
        }
        coder.context[SWFDecoder.ArrayCountExtended] = 1;
        decodeShape(coder);
        coder.context[SWFDecoder.ArrayCountExtended] = 0;
        coder.endObject("DefineShape2");
    }

    static void decodeDefineShape3(SWFDecoder coder)
    {
        coder.beginObject("defineShape3", "DefineShape3");
        decodeDefineObject(coder);

        coder.context[SWFDecoder.TransparentColors] = 1;

        decodeBounds("bounds", coder); // bounds

        int fillStyleCount = coder.readWord("fillStyleCount", 1, false);

        if (fillStyleCount > 0)
        {
            if (fillStyleCount == 0xFF)
                fillStyleCount = coder.readWord("fillStyleCountExtended", 2, false);

            coder.beginObject("fillStyles", "Array");

            for (int i=0; i<fillStyleCount; i++)
            {
                switch (coder.scanWord(1, false))
                {
                    case Solid:
                        decodeSolidFill(coder);
                        break;
                    case Linear:
                        decodeGradientFill(coder);
                        break;
                    case Radial:
                        decodeGradientFill(coder);
                        break;
                    case Tiled:
                    case Unsmoothed_Tiled:
                        decodeBitmapFill(coder);
                        break;
                    case Clipped:
                    case Unsmoothed_Clipped:
                        decodeBitmapFill(coder);
                        break;
                }
            }
            coder.endObject("Array");
        }

        int lineStyleCount = coder.readWord("lineStyleCount", 1, false);

        if (lineStyleCount > 0)
        {
            if (lineStyleCount == 0xFF)
                lineStyleCount = coder.readWord("lineStyleCountExtended", 2, false);

            coder.beginObject("lineStyles", "Array");

            for (int i=0; i<lineStyleCount; i++)
                decodeSolidLine(coder);

            coder.endObject("Array");
        }
        coder.context[SWFDecoder.ArrayCountExtended] = 1;

        decodeShape(coder);

        coder.context[SWFDecoder.ArrayCountExtended] = 0;
        coder.context[SWFDecoder.TransparentColors] = 0;
        coder.endObject("DefineShape3");
    }

    static void decodeDefineText2(SWFDecoder coder)
    {
        coder.beginObject("defineText2", "DefineText2");
        decodeDefineObject(coder); // length

        coder.context[SWFDecoder.TransparentColors] = 1;

        decodeBounds("bounds", coder);

        // This code is used to get round a bug in Flash - sometimes 16,
        // 8-bit zeroes are written out before the transform. The root
        // cause in Flash is unknown but seems to be related to the
        // bounds not being set - all values are zero.

        boolean bytesAreZero = true;
        int start = coder.getPointer();

        for (int i=0; i<16; i++)
        {
            if (coder.scanWord(1, false) != 0)
                 bytesAreZero = false;

             coder.adjustPointer(8);
        }

        coder.setPointer(start);

        if (bytesAreZero)
        {
            for (int i=0; i<16; i++)
            {
                coder.scanWord(1, false);
                coder.adjustPointer(8);
            }
        }

        decodeCoordTransform("coordTransform", coder);

        coder.context[SWFDecoder.NumberOfGlyphBits] = coder.readWord("numberOfGlyphBits", 1, false);
        coder.context[SWFDecoder.NumberOfAdvanceBits] = coder.readWord("numberOfAdvanceBits", 1, false);

        coder.beginObject("textRecords", "Array");

        while (coder.scanBits(8, false) != 0)
            decodeTextRecord(coder);

        coder.readBits("endOfText", 8, false);
        coder.endObject("Array");

        coder.context[SWFDecoder.NumberOfGlyphBits] = 0;
        coder.context[SWFDecoder.NumberOfAdvanceBits] = 0;
        coder.context[SWFDecoder.TransparentColors] = 0;
        coder.endObject("DefineText2");
    }

    static void decodeDefineTextField(SWFDecoder coder)
    {
        coder.beginObject("defineTextField", "DefineTextField");
        decodeDefineObject(coder);

        coder.context[SWFDecoder.TransparentColors] = 1;

        decodeBounds("bounds", coder);

        boolean _containsText = coder.readBool("hasText");
        coder.readBool("isWordWrapped");
        coder.readBool("isMultiline");
        coder.readBool("isPassword");
        coder.readBool("isReadOnly");
        boolean _containsColor = coder.readBool("hasColor");
        boolean _containsMaxLength = coder.readBool("hasMaxLength");
        boolean _containsFont = coder.readBool("hasFont");
        coder.readBits("reserved1", 1, false);
        coder.readBool("isAutoSize");
        boolean _containsLayout = coder.readBool("hasLayout");
        coder.readBool("isSelectable");
        coder.readBool("isBordered");
        coder.readBool("reserved2");
        coder.readBool("isHTML");
        coder.readBool("useFont");

        if (_containsFont)
        {
            coder.readWord("fontIdentifier", 2, false);
            coder.readWord("fontHeight", 2, false);
        }

        if (_containsColor)
            decodeColor("color", coder);

        if (_containsMaxLength)
            coder.readWord("maxLength", 2, false);

        if (_containsLayout)
        {
            coder.readWord("alignment", 1, false);
            coder.readWord("leftMargin", 2, false);
            coder.readWord("rightMargin", 2, false);
            coder.readWord("indent", 2, false);
            coder.readWord("leading", 2, true);
        }
        coder.readString("variableName");

        if (_containsText)
            coder.readString("initialText");

        coder.context[SWFDecoder.TransparentColors] = 0;
        coder.endObject("DefineTextField");
    }

    static void decodeDefineVideo(SWFDecoder coder)
    {
        coder.beginObject("defineVideo", "DefineVideo");
        decodeDefineObject(coder);

        coder.readWord("frameCount", 2, false);
        coder.readWord("width", 2, false);
        coder.readWord("height", 2, false);
        coder.readBits("reserved", 5, false);
        coder.readBits("deblocking", 2, false);
        coder.readBool("smoothing");
        coder.readWord("codec", 1, false);
        coder.endObject("DefineVideo");
    }
}
